﻿// FormsAuthenticationService.cs
//

using System;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.Security;

namespace System.ServiceModel.DomainServices.Server.ApplicationServices {

    /// <summary>
    /// A base class for implementing an authentication domain service that relies on
    /// forms authentication.
    /// </summary>
    /// <typeparam name="TUser">The type of the user object associated with this service.</typeparam>
    public abstract class FormsAuthenticationService<TUser> : DomainService, IAuthentication<TUser> where TUser : UserBase, new() {

        private const int DefaultCookieLifetime = 30;

        private TimeSpan _cookieLifetime;
        private bool _enableRoles;

        /// <summary>
        /// Initializes an instance of a FormsAuthenticationService.
        /// </summary>
        /// <param name="enableRoles">Whether the application uses roles.</param>
        protected FormsAuthenticationService(bool enableRoles) {
            _enableRoles = enableRoles;
            _cookieLifetime = TimeSpan.FromMinutes(30);
        }

        /// <summary>
        /// Gets or sets the lifetime of the HTTP cookies used to create an
        /// authenticated session.
        /// </summary>
        protected TimeSpan CookieLifetime {
            get {
                return _cookieLifetime;
            }
            set {
                _cookieLifetime = value;
            }
        }

        /// <summary>
        /// Deserializes a user object from the serialized representation. The default
        /// implementation matches SerializeUser and performs JSON deserialization.
        /// </summary>
        /// <param name="userData">The serialized representation of the user.</param>
        /// <returns>The deserialized user.</returns>
        protected virtual TUser DeserializeUser(string userData) {
            if (String.IsNullOrEmpty(userData)) {
                throw new ArgumentNullException("userData");
            }

            JavaScriptSerializer serializer = new JavaScriptSerializer();
            return serializer.Deserialize<TUser>(userData);
        }

        /// <summary>
        /// Gets the user object representing the default anonymous principal. The default
        /// implementation returns an empty user object.
        /// </summary>
        /// <returns>The default user object.</returns>
        protected virtual TUser GetDefaultUser() {
            return new TUser();
        }

        /// <summary>
        /// Gets the user object representing the currently authenticated principal.
        /// </summary>
        /// <returns>The user object, or the default user object if there is no authenticated principal.</returns>
        public TUser GetUser() {
            HttpContextBase httpContext = (HttpContextBase)ServiceContext.GetService(typeof(HttpContextBase));
            TUser user = (TUser)httpContext.Items[typeof(TUser)];

            if (user != null) {
                return user;
            }

            IPrincipal currentUser = ServiceContext.User;
            if ((currentUser != null) || currentUser.Identity.IsAuthenticated) {
                FormsAuthenticationTicket ticket = null;

                FormsIdentity userIdentity = currentUser.Identity as FormsIdentity;
                if (userIdentity != null) {
                    ticket = userIdentity.Ticket;
                    if (ticket != null) {
                        user = DeserializeUser(ticket.UserData);
                    }
                }
            }

            if (user == null) {
                user = GetDefaultUser();
            }

            httpContext.Items[typeof(TUser)] = user;
            return user;
        }

        /// <summary>
        /// Performs the login using the specified credentials and returns the corresponding
        /// user object.
        /// </summary>
        /// <param name="userName">The name of the user.</param>
        /// <param name="password">The password of the user.</param>
        /// <param name="isPersistent">Whether to create a persistent authenticated session.</param>
        /// <param name="customData">Any implementation specific data to use during authentication.</param>
        /// <returns>The user object if the credentials were valid.</returns>
        public TUser Login(string userName, string password, bool isPersistent, string customData) {
            TUser user = ValidateCredentials(userName, password, customData);

            if (user != null) {
                string userData = SerializeUser(user);

                FormsAuthenticationTicket ticket =
                    ticket = new FormsAuthenticationTicket(/* version */ 1, userName,
                                                           DateTime.Now, DateTime.Now.Add(CookieLifetime),
                                                           isPersistent,
                                                           userData,
                                                           FormsAuthentication.FormsCookiePath);

                string encryptedTicket = FormsAuthentication.Encrypt(ticket);
                HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);

                HttpContextBase httpContext = (HttpContextBase)ServiceContext.GetService(typeof(HttpContextBase));
                httpContext.Response.Cookies.Add(authCookie);

                if (_enableRoles) {
                    FormsAuthenticationHelper.Enable(this.GetType());
                }
            }

            return user;
        }

        /// <summary>
        /// Logs out the current user.
        /// </summary>
        /// <returns>The default user object.</returns>
        public TUser Logout() {
            FormsAuthentication.SignOut();
            return GetDefaultUser();
        }

        /// <summary>
        /// Updates the information about the user.
        /// </summary>
        /// <param name="user">The user to update.</param>
        public void UpdateUser(TUser user) {
            // TODO: Implement this (first validate it is the current user)
            throw new NotImplementedException();
        }

        /// <summary>
        /// Serializes the user object so it can be saved into the forms authentication
        /// cookie. The framework will automatically encrypt the result. The default
        /// implementation serializes the user into JSON format.
        /// </summary>
        /// <param name="user">The user to serialize.</param>
        /// <returns>The serialized representation of the user.</returns>
        protected virtual string SerializeUser(TUser user) {
            if (user == null) {
                throw new ArgumentNullException("user");
            }

            JavaScriptSerializer serializer = new JavaScriptSerializer();
            return serializer.Serialize(user);
        }

        /// <summary>
        /// Validates the specified credentials.
        /// </summary>
        /// <param name="name">The name of the user.</param>
        /// <param name="password">The password of the user.</param>
        /// <param name="customData">Any implementation specific data to use during authentication.</param>
        /// <returns>The user object if the credentials are valid.</returns>
        protected abstract TUser ValidateCredentials(string name, string password, string customData);
    }
}
